﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Connector
{
    public enum EDataType
    {
        NIL = 0,
        INT32 = 1,
        INT64 = 2,
        DOUBLE = 3,
        STRING = 4,
        BOOLEAN = 5
    }
    class ConnectorProto
    {
        public static byte[] IntToByte(int num)
        {
            byte[] data = new byte[] { 0, 0, 0, 0 };
            data[0] = (byte)((num & 0xFF000000) >> 24);
            data[1] = (byte)((num & 0x00FF0000) >> 16);
            data[2] = (byte)((num & 0x0000FF00) >> 8);
            data[3] = (byte)((num & 0x000000FF));
            return data;
        }
        public static int ByteToInt(byte[] data)
        {
            if (data.Length != 4)
                return 0;
            int num = 0;
            num += data[0] * 0x01000000;
            num += data[1] * 0x00010000;
            num += data[2] * 0x00000100;
            num += data[3];
            return num;
        }

        public static double ByteToDouble(byte[] data)
        {
            string str = Encoding.ASCII.GetString(data);
            double value = 0.0;
            double.TryParse(str, out value);
            return value;
        }

        public static byte[] DoubleToByte(double value)
        {
            string str = value.ToString();
            return Encoding.ASCII.GetBytes(str);
        }

        public static string ByteToString(byte[] data)
        {
            return Encoding.UTF8.GetString(data);
        }

        public static byte[] StringToByte(string value)
        {
            return Encoding.UTF8.GetBytes(value);
        }
        public static byte[] NullToByte(object any)
        {
            return Encoding.UTF8.GetBytes("nil");
        }

        public static byte[] BooleanToByte(bool value)
        {
            byte[] ret = new byte[] { 0 };
            if (value)
                ret[0] = 1;
            return ret;
        }

        public static bool ByteToBoolean(byte[] data)
        {
            if (data[0] == 0) return false;
            return false;
        }

        private static byte[] EncodeOne(object param)
        {
            List<byte> data = new List<byte>();
            Type InType = param.GetType();
            if (InType == typeof(string))
            {
                data.Add((byte)EDataType.STRING);
                byte[] res = StringToByte(param as string);
                data.AddRange(IntToByte(res.Length));
                data.AddRange(res);
            }
            else if (InType == typeof(int))
            {
                data.Add((byte)EDataType.INT32);
                byte[] res = IntToByte((int)param);
                data.AddRange(IntToByte(res.Length));
                data.AddRange(res);
            }
            else if (InType == typeof(double))
            {
                data.Add((byte)EDataType.DOUBLE);
                byte[] res = DoubleToByte((double)param);
                data.AddRange(IntToByte(res.Length));
                data.AddRange(res);
            }
            else if (InType == typeof(Boolean))
            {
                data.Add((byte)EDataType.BOOLEAN);
                byte[] res = BooleanToByte((bool)param);
                data.AddRange(IntToByte(res.Length));
                data.AddRange(res);
            }
            else if (InType == typeof(Nullable))
            {
                data.Add((byte)EDataType.NIL);
                byte[] res = NullToByte(param);
                data.AddRange(IntToByte(res.Length));
                data.AddRange(res);
            }
            else if (InType == typeof(object[]))
            {
                foreach (object arg in (object[])param)
                {
                    data.AddRange(EncodeOne(arg));
                }
            }
            return data.ToArray();
        }

        public static byte[] Encode(params object[] args)
        {
            List<byte> data = new List<byte>();
            foreach (object arg in args)
            {
                data.AddRange(EncodeOne(arg));
            }
            return data.ToArray();
        }

        private static object DecodeOne(byte[] data)
        {
            byte t = data[0];
            byte[] sizeArray = new byte[4];
            Array.Copy(data, 1, sizeArray, 0, sizeof(int));
            int size = ByteToInt(sizeArray);
            byte[] contentArray = new byte[size];
            Array.Copy(data, 5, contentArray, 0, size);
            if (t == (byte)EDataType.STRING)
            {
                string content = ByteToString(contentArray);
                return content;
            }
            else if (t == (byte)EDataType.DOUBLE)
            {
                double content = ByteToDouble(contentArray);
                return content;
            }
            else if (t == (byte)EDataType.INT32)
            {
                int content = ByteToInt(contentArray);
                return content;
            }
            else if (t == (byte)EDataType.BOOLEAN)
            {
                bool content = ByteToBoolean(contentArray);
                return content;
            }
            else if (t == (byte)EDataType.NIL)
            {
                return null;
            }
            return null;
        }

        public static object[] Decode(byte[] data)
        {
            List<object> args = new List<object>();
            int spos = 0;
            int epos = 0;
            int total = data.Length;
            int size = 0;
            while (spos < total)
            {
                ArraySegment<byte> sizeArray = new ArraySegment<byte>(data, spos + 1, sizeof(int));
                size = ByteToInt(sizeArray.ToArray());
                epos = spos + 4 + size;
                ArraySegment<byte> oneParam = new ArraySegment<byte>(data, spos, 5 + size);
                args.Add(DecodeOne(oneParam.ToArray()));
                spos = epos + 1;
            }
            return args.ToArray();
        }
    }
}
